//////////////////////////////////////////////////////////////////////
// CubemapSampler.h
//
// SHEN Fangyang
// me@shenfy.com
//
// Copyright (C) SHEN Fangyang, 2011, All rights reserved.
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
//  Brief Description
//
//	Cubemap image samplers,
//	CHCrossCubemapSampler for horizontal cross layout (D3D),
//  CVCrossCubemapSampler for vertical cross layout (HDRShop).
//
//////////////////////////////////////////////////////////////////////
#pragma once

#include <IImgSampler.h>
#include <cmath>
#include <Logger.h>

#pragma comment (lib, W32LIB_BIN_FILE)

////////////////////////////////////////////////
// (u,v) origin at upper-left corner!!!
////////////////////////////////////////////////

namespace grflib
{
	namespace image
	{
		//Class declaration

		/////////////////////////////////////////////////////////
		//Cubemap Sampler for Horizontal Strip Layout Images
		//in Direct3D DDS order
		/////////////////////////////////////////////////////////
		template<typename T>
		class CCubemapSampler :
			public IImgSampler<T>
		{
		public:
			CCubemapSampler(void);
			virtual ~CCubemapSampler(void);

			virtual void BindImage(w32lib::CImgTmpl<T> *pImg);		

			virtual T texCUBE(const float &x, const float &y, const float &z, const UINT &c);
			virtual void texCUBE(const float &x, const float &y, const float &z, T *pixel/*output*/);

			//conversion between 3D direction vector to 2D (u,v) + face index
			static void Tex3DTo2D(const float &x, const float &y, const float &z, /*input*/
								  float &u, float &v, UINT &faceId /*output*/);
			static void Tex2DTo3D(const float &u, const float &v, const UINT faceId /*input*/,
								  float &x, float &y, float &z /*output*/);

            T &operator() (const UINT &face, const UINT &x, const UINT &y, const UINT &c);
			const T &operator() (const UINT &face, const UINT &x, const UINT &y, const UINT &c) const;

		protected:
			UINT m_faceWidth, m_faceHeight;
			UINT m_faceCoord[6][2];
		};

		///////////////////////////////////////////////////////
		//Cubemap Sampler for Horizontal Cross Layout Images
		//(Ex. Direct3D DDS cubemaps)
		///////////////////////////////////////////////////////
		template<typename T>
		class CHCrossCubemapSampler :
			public CCubemapSampler<T>
		{
		public:
			CHCrossCubemapSampler(void);
			virtual ~CHCrossCubemapSampler(void);

			virtual void BindImage(w32lib::CImgTmpl<T> *pImg);	//override
		};

		///////////////////////////////////////////////////////
		//Cubemap Sampler for Vertical Cross Layout Images
		//(Ex. HDRShop cubemaps)
		///////////////////////////////////////////////////////
		template<typename T>
		class CVCrossCubemapSampler :
			public CCubemapSampler<T>
		{
		public:
			CVCrossCubemapSampler(void);
			virtual ~CVCrossCubemapSampler(void);

			//overrided methods:
			virtual void BindImage(w32lib::CImgTmpl<T> *pImg);
			virtual T texCUBE(const float &x, const float &y, const float &z, const UINT &c);
			virtual void texCUBE(const float &x, const float &y, const float &z, T *pixel/*output*/);

            //conversion between 3D direction vector to 2D (u,v) + face index
			static void Tex3DTo2D(const float &x, const float &y, const float &z, /*input*/
								  float &u, float &v, UINT &faceId /*output*/);
			static void Tex2DTo3D(const float &u, const float &v, const UINT faceId /*input*/,
								  float &x, float &y, float &z /*output*/);
		};


		//Class definition
		
		////////////////////////////////////////////////
		//Cubemap Sampler for Horizontal Layout Images
		////////////////////////////////////////////////
		template<typename T>
		CCubemapSampler<T>::CCubemapSampler() :
			m_faceWidth(0),
			m_faceHeight(0)
		{
			memset(m_faceCoord, 0, sizeof(UINT) * 12);
		}

		template<typename T>
		CCubemapSampler<T>::~CCubemapSampler()
		{
		}

		template<typename T>
		void CCubemapSampler<T>::Tex3DTo2D(const float &x, const float &y, const float &z, /*input*/
								  float &u, float &v, UINT &faceId /*output*/)
		{
			float absx, absy, absz;
			float ma, sc, tc;

			absx = abs(x);
			absy = abs(y);
			absz = abs(z);

			if (absx >= absy && absx >= absz) //x is the major axis direction
			{
				ma = absx;
				if (x > 0.0f)
				{
					sc = -z; tc = -y;
					faceId = 0;
				}
				else
				{
					sc = z; tc = -y;
					faceId = 1;
				}
			}
			else if (absy >= absx && absy >= absz) //y is major axis direction
			{
				ma = absy;
				if (y > 0.0f)
				{
					sc = x; tc = z;
					faceId = 2;
				}
				else
				{
					sc = x; tc = -z;
					faceId = 3;
				}
			}
			else //z is major axis direction
			{
				ma = absz;
				if (z > 0.0f)
				{
					sc = x; tc = -y;
					faceId = 4;
				}
				else
				{
					sc = -x; tc = -y;
					faceId = 5;
				}
			}

			//normalized 2D texture coordinate on [0, 1]
			u = (sc / ma + 1.0f) * 0.5f;
			v = (tc / ma + 1.0f) * 0.5f;
		}

		template<typename T>
		void CCubemapSampler<T>::Tex2DTo3D(const float &u, const float &v, const UINT faceId /*input*/,
										   float &x, float &y, float &z /*output*/)
		{
			float sc, tc;

			sc = u * 2.0f - 1.0f;
			tc = v * 2.0f - 1.0f;

			switch (faceId)
			{
			case 0:
				z = -sc;
				y = -tc;
				x = 1.0f;
				break;
			case 1:
				z = sc;
				y = -tc;
				x = -1.0f;
				break;
			case 2:
				x = sc;
				z = tc;
				y = 1.0f;
				break;
			case 3:
				x = sc;
				z = -tc;
				y = -1.0f;
				break;
			case 4:
				x = sc;
				y = -tc;
				z = 1.0f;
				break;
			case 5:
				x = -sc;
				y = -tc;
				z = -1.0f;
				break;
			default:
				//leave everything untouched
				w32lib::CLogger::Instance()->Error(_T("Invalid face index!"));
				return;
				break;
			}

			math::Normalize3D(x, y, z);
		}

		template<typename T>
		void CCubemapSampler<T>::BindImage(w32lib::CImgTmpl<T> *pImg)
		{
			IImgSampler<T>::BindImage(pImg);

			m_faceWidth = m_width / 6;
			m_faceHeight = m_height;

			for (UINT i = 0; i < 6; i++)
			{
				m_faceCoord[i][0] = m_faceWidth * i;
				m_faceCoord[i][1] = 0;
			}
		}

		template<typename T>
		T CCubemapSampler<T>::texCUBE(const float &x, const float &y, const float &z, const UINT &c)
		{
			float u = 0.0f, v = 0.0f;
			UINT faceId = 0;

			Tex3DTo2D(x, y, z, u, v, faceId);

			float xf, yf;
			UINT x0, y0, x1, y1;

			//xf = u * (m_faceWidth - 1)  + m_faceCoord[faceId][0];
			//yf = v * (m_faceHeight - 1)  + m_faceCoord[faceId][1];
			xf = math::Saturate(u * m_faceWidth - 0.5f, 0.0f, m_faceWidth - 1.0f) + m_faceCoord[faceId][0];
			yf = math::Saturate(v * m_faceHeight - 0.5f, 0.0f, m_faceHeight - 1.0f) + m_faceCoord[faceId][1];
			x0 = (UINT)floor(xf);
			y0 = (UINT)floor(yf);
			x1 = (UINT)ceil(xf);
			y1 = (UINT)ceil(yf);

			float w[4] = {0};
			math::BilinearWeight(xf, yf, w);

			T result = (T)((*m_pImg)(x0, y0, c) * w[0]
			+ (*m_pImg)(x1, y0, c) * w[1]
			+ (*m_pImg)(x0, y1, c) * w[2]
			+ (*m_pImg)(x1, y1, c) * w[3]);

			return result;
		}

		template<typename T>
		void CCubemapSampler<T>::texCUBE(const float &x, const float &y, const float &z, T *pixel/*output*/)
		{
			float u = 0.0f, v = 0.0f;
			UINT faceId = 0;

			Tex3DTo2D(x, y, z, u, v, faceId);

			float xf, yf;
			UINT x0, y0, x1, y1;

			//xf = u * (m_faceWidth - 1)  + m_faceCoord[faceId][0];
			//yf = v * (m_faceHeight - 1)  + m_faceCoord[faceId][1];
			xf = math::Saturate(u * m_faceWidth - 0.5f, 0.0f, m_faceWidth - 1.0f) + m_faceCoord[faceId][0];
			yf = math::Saturate(v * m_faceHeight - 0.5f, 0.0f, m_faceHeight - 1.0f) + m_faceCoord[faceId][1];
			x0 = (UINT)floor(xf);
			y0 = (UINT)floor(yf);
			x1 = (UINT)ceil(xf);
			y1 = (UINT)ceil(yf);

			float w[4] = {0};
			math::BilinearWeight(xf, yf, w);

			for (UINT c = 0; c < m_pImg->GetBPP() / 8 / sizeof(T); c++)
			{
				pixel[c] = (T)((*m_pImg)(x0, y0, c) * w[0]
							+ (*m_pImg)(x1, y0, c) * w[1]
							+ (*m_pImg)(x0, y1, c) * w[2]
							+ (*m_pImg)(x1, y1, c) * w[3]);
			}
		}

        template<typename T>
		T &CCubemapSampler<T>::operator()(const UINT &face, const UINT &x, const UINT &y, const UINT &c)
		{
			//not checking m_pImg != 0 in release mode for performance concern
			_ASSERT(m_pImg != 0);
            return (*m_pImg)(x + m_faceCoord[face][0], y + m_faceCoord[face][1], c);
		}

		template<typename T>
		const T &CCubemapSampler<T>::operator()(const UINT &face, const UINT &x, const UINT &y, const UINT &c) const
		{
			//not checking m_pImg != 0 in release mode for performance concern
			_ASSERT(m_pImg != 0);
            return (*m_pImg)(x + m_faceCoord[face][0], y + m_faceCoord[face][1], c);
		}

		/////////////////////////////////////////////////////
		//Cubemap Sampler for Horizontal Cross Layout Images
		/////////////////////////////////////////////////////

		template<typename T>
		CHCrossCubemapSampler<T>::CHCrossCubemapSampler()
		{
			memset(m_faceCoord, 0, sizeof(UINT) * 12);
		}

		template<typename T>
		CHCrossCubemapSampler<T>::~CHCrossCubemapSampler()
		{
		}

		template<typename T>
		void CHCrossCubemapSampler<T>::BindImage(w32lib::CImgTmpl<T> *pImg)
		{
			IImgSampler<T>::BindImage(pImg);
			
			m_faceWidth = m_width / 4;
			m_faceHeight = m_height / 3;

			m_faceCoord[0][0] = 2 * m_faceWidth;
			m_faceCoord[0][1] = m_faceHeight;
			m_faceCoord[1][0] = 0;
			m_faceCoord[1][1] = m_faceHeight;
			m_faceCoord[2][0] = m_faceWidth;
			m_faceCoord[2][1] = 0;
			m_faceCoord[3][0] = m_faceWidth;
			m_faceCoord[3][1] = 2 * m_faceHeight;
			m_faceCoord[4][0] = m_faceWidth;
			m_faceCoord[4][1] = m_faceHeight;
			m_faceCoord[5][0] = 3 * m_faceWidth;
			m_faceCoord[5][1] = m_faceHeight;
		}

		/////////////////////////////////////////////////////
		//Cubemap Sampler for Vertical Cross Layout Images
		//(Ex. HDRShop cubemaps)
		/////////////////////////////////////////////////////
		template<typename T>
		CVCrossCubemapSampler<T>::CVCrossCubemapSampler()
		{
			memset(m_faceCoord, 0, sizeof(UINT) * 12);
		}

		template<typename T>
		CVCrossCubemapSampler<T>::~CVCrossCubemapSampler()
		{
		}

		template<typename T>
		void CVCrossCubemapSampler<T>::BindImage(w32lib::CImgTmpl<T> *pImg)
		{
			IImgSampler<T>::BindImage(pImg);

			m_faceWidth = m_width / 3;
			m_faceHeight = m_height / 4;

			m_faceCoord[0][0] = 2 * m_faceWidth;
			m_faceCoord[0][1] = m_faceHeight;
			m_faceCoord[1][0] = 0;
			m_faceCoord[1][1] = m_faceHeight;
			m_faceCoord[2][0] = m_faceWidth;
			m_faceCoord[2][1] = 0;
			m_faceCoord[3][0] = m_faceWidth;
			m_faceCoord[3][1] = 2 * m_faceHeight;
			m_faceCoord[4][0] = m_faceWidth;
			m_faceCoord[4][1] = m_faceHeight;
			m_faceCoord[5][0] = m_faceWidth;
			m_faceCoord[5][1] = 3 * m_faceHeight;
		}

		template<typename T>
		T CVCrossCubemapSampler<T>::texCUBE(const float &x, const float &y, const float &z, const UINT &c)
		{
			float u = 0.0f, v = 0.0f;
			UINT faceId = 0;

			CVCrossCubemapSampler<T>::Tex3DTo2D(x, y, z, u, v, faceId);

			float xf, yf;
			UINT x0, y0, x1, y1;

			//xf = u * (m_faceWidth - 1)  + m_faceCoord[faceId][0];
			//yf = v * (m_faceHeight - 1)  + m_faceCoord[faceId][1];
			xf = math::Saturate(u * m_faceWidth - 0.5f, 0.0f, m_faceWidth - 1.0f) + m_faceCoord[faceId][0];
			yf = math::Saturate(v * m_faceHeight - 0.5f, 0.0f, m_faceHeight - 1.0f) + m_faceCoord[faceId][1];
			x0 = (UINT)floor(xf);
			y0 = (UINT)floor(yf);
			x1 = (UINT)ceil(xf);
			y1 = (UINT)ceil(yf);

			float w[4] = {0};
			math::BilinearWeight(xf, yf, w);

			T result = (T)((*m_pImg)(x0, y0, c) * w[0]
			+ (*m_pImg)(x1, y0, c) * w[1]
			+ (*m_pImg)(x0, y1, c) * w[2]
			+ (*m_pImg)(x1, y1, c) * w[3]);

			return result;
		}

		template<typename T>
		void CVCrossCubemapSampler<T>::texCUBE(const float &x, const float &y, const float &z, T *pixel/*output*/)
		{
			float u = 0.0f, v = 0.0f;
			UINT faceId = 0;

			CVCrossCubemapSampler<T>::Tex3DTo2D(x, y, z, u, v, faceId);

			float xf, yf;
			UINT x0, y0, x1, y1;

			//xf = u * (m_faceWidth - 1) + m_faceCoord[faceId][0];
			//yf = v * (m_faceHeight - 1) + m_faceCoord[faceId][1];
			xf = math::Saturate(u * m_faceWidth - 0.5f, 0.0f, m_faceWidth - 1.0f) + m_faceCoord[faceId][0];
			yf = math::Saturate(v * m_faceHeight - 0.5f, 0.0f, m_faceHeight - 1.0f) + m_faceCoord[faceId][1];
			x0 = (UINT)floor(xf);
			y0 = (UINT)floor(yf);
			x1 = (UINT)ceil(xf);
			y1 = (UINT)ceil(yf);

			float w[4] = {0};
			math::BilinearWeight(xf, yf, w);

			for (UINT c = 0; c < m_pImg->GetBPP() / 8 / sizeof(T); c++)
			{
				pixel[c] = (T)((*m_pImg)(x0, y0, c) * w[0]
							+ (*m_pImg)(x1, y0, c) * w[1]
							+ (*m_pImg)(x0, y1, c) * w[2]
							+ (*m_pImg)(x1, y1, c) * w[3]);
			}
		}

        template<typename T>
		void CVCrossCubemapSampler<T>::Tex3DTo2D(const float &x, const float &y, const float &z, /*input*/
								  float &u, float &v, UINT &faceId /*output*/)
		{
            CCubemapSampler<T>::Tex3DTo2D(x, y, z, u, v, faceId);
            if (5 == faceId) //flip (u,v) for face #5
            {
                u = 1.0f - u;
                v = 1.0f - v;
            }
		}

		template<typename T>
		void CVCrossCubemapSampler<T>::Tex2DTo3D(const float &u, const float &v, const UINT faceId /*input*/,
										   float &x, float &y, float &z /*output*/)
		{
            if (5 == faceId) //flip (u,v) for face #5
                CCubemapSampler<T>::Tex2DTo3D(1.0f - u, 1.0f - v, faceId, x, y, z);
            else
                CCubemapSampler<T>::Tex2DTo3D(u, v, faceId, x, y, z);
		}

	} //namespace namespace image

} //namespace namespace grflib